"""
通用附件服务，负责文件上传、删除等存储操作
"""
from __future__ import annotations

import io
import logging
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional, Tuple

from fastapi import UploadFile  # pyright: ignore[reportMissingImports]
from minio.error import S3Error  # pyright: ignore[reportMissingImports]

from app.core.config import settings
from app.core.database import get_minio

logger = logging.getLogger(__name__)


class AttachmentService:
    """
    提供统一的附件存储能力，避免在各模块重复编写上传/删除逻辑
    """

    def __init__(self) -> None:
        self._bucket_name = settings.MINIO_BUCKET_NAME
        self._default_max_size = getattr(settings, "MAX_FILE_SIZE", 50 * 1024 * 1024)

    async def upload(
        self,
        *,
        file: UploadFile,
        directory: str,
        filename_prefix: Optional[str] = None,
        max_size: Optional[int] = None
    ) -> Dict[str, Any]:
        """
        上传文件到对象存储，并返回附件基础信息
        """
        if not file:
            raise ValueError("文件不存在")

        content = await file.read()
        file_size = len(content)
        limit = max_size or self._default_max_size
        if file_size > limit:
            raise ValueError(f"文件大小超过最大允许限制 ({limit / 1024 / 1024:.1f}MB)")

        original_filename = file.filename or ""
        file_extension = Path(original_filename).suffix.lower()
        prefix = filename_prefix or "attachment"
        unique_filename = f"{prefix}_{uuid.uuid4()}{file_extension}"

        object_path = f"{directory.rstrip('/')}/{unique_filename}"
        stream = io.BytesIO(content)

        client = get_minio()
        client.put_object(
            self._bucket_name,
            object_path,
            stream,
            length=file_size,
            content_type=file.content_type or "application/octet-stream",
        )

        return {
            "filename": unique_filename,
            "original_filename": original_filename,
            "file_size": file_size,
            "file_type": file.content_type or "application/octet-stream",
            "uploaded_at": datetime.utcnow(),
        }

    def delete(self, *, directory: str, filename: str) -> None:
        """
        从对象存储中删除指定文件
        """
        object_path = f"{directory.rstrip('/')}/{filename}"
        client = get_minio()
        try:
            client.remove_object(self._bucket_name, object_path)
        except S3Error as exc:  # pragma: no cover - 存储异常记录日志即可
            if exc.code != "NoSuchKey":
                logger.warning(
                    "Failed to remove attachment object from storage: %s", object_path, exc_info=True
                )
            # 未找到对象时忽略，保持幂等

    def download(self, *, directory: str, filename: str) -> Tuple[bytes, str]:
        """
        从对象存储中下载文件并返回二进制数据与 content-type
        """
        object_path = f"{directory.rstrip('/')}/{filename}"
        client = get_minio()
        try:
            response = client.get_object(self._bucket_name, object_path)
        except S3Error as exc:
            if exc.code == "NoSuchKey":
                raise FileNotFoundError(f"Attachment {filename} not found") from exc
            raise

        try:
            data = response.read()
            headers = getattr(response, "headers", None)
            content_type = headers.get("Content-Type") if headers else None
            return data, content_type or "application/octet-stream"
        finally:
            response.close()
            response.release_conn()

